Web應用程式本身的機制並不適合用來作為執行需要長時程運行的需求,而這類需求卻很常見,而常見的解決方式是
第1種方式並不是一種正確的解決方式,嚴格來說不應該這樣做。
第2種方式很容易達成,但有個缺點,使用者無法得知執行時的狀態,因此無法確認到底是失敗了還是仍然在執行中,這個問題可以透過一些設計解決,但都非常複雜。
目前在Asp.Net Core已經提供了「託管服務」IHostedService,可以解決Web本身不適合做為長時程執行的問題,然而單靠「託管服務」要實現整體的完整機制並不容易,而Quaerz.Net這個老牌的排程程式庫能弭補這個問題,目前新版的Quaerz.Net 3已與Asp.Net Core已有很好的整合。
底下教學目標達成下列需求
完整的程式可由此下載
首先透過nuget將Quartz.Net引用至專案
Install-Package Quartz.AspNetCore
修改Startup.cs加入Quartz.Net的初始化相關程式
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddQuartz(q =>
{
q.UseMicrosoftDependencyInjectionScopedJobFactory();
q.UseSimpleTypeLoader();
q.UseInMemoryStore();
q.UseDefaultThreadPool(tp =>
{
tp.MaxConcurrency = 10;
});
var jobKey = DemoJob.JobKey;
q.AddJob<DemoJob>(jobKey, j => j
.StoreDurably()
.WithDescription("demo job")
);
});
// ASP.NET Core hosting
services.AddQuartzServer(options =>
{
options.WaitForJobsToComplete = true;
});
}
這裡為了展示只簡單的使用一些基本配置,想要更深入的了解Quartz.Net可以參考官方的quick start。
上面程式中有個地方須注意因為這邊的DemoJob並沒有關聯到任何的Trigger,Quartz.Net預設情況下會自動刪除因而會出現錯誤,因此要加上StoreDurably()。
撰寫Job
public class DemoJob : IJob
{
public readonly static JobKey JobKey = new JobKey(nameof(DemoJob), JobKey.DefaultGroup);
public async Task Execute(IJobExecutionContext context)
{
await Task.Run(() =>
{
for (var i = 0; i < 5; i++)
{
var message = $"第{i}次執行";
Console.WriteLine(message);
context.Result = message;
context.CancellationToken.WaitHandle.WaitOne(TimeSpan.FromSeconds(5));
}
}, context.CancellationToken);
}
}
DemoJob作為展示,單純的透過Console.WriteLine輸出執行狀態到Asp.Net core的Host上,因此如果使用IIS作為Host畫面上是不會看到任何輸出,所以記得要切換至專案作為Host
上面程式中的context.Result為Quartz.Net中提供的回報執行狀態機制,後面我們會透過這個參數來取得目前Job中的執行狀態,其物件形態為Object也就是說可以自定義想需要的內容,這邊單純的字串來回報。
新增TaskController與NewTask方法,用來建立Task的Trigger,讓UI觸發執行。
public async Task<IActionResult> NewTask([FromServices] ISchedulerFactory schedulerFactory, CancellationToken cancellationToken)
{
var scheduler = await schedulerFactory.GetScheduler(cancellationToken);
var trigger = TriggerBuilder.Create()
.ForJob(DemoJob.JobKey)
.StartNow()
.WithSimpleSchedule(x => x.WithRepeatCount(0))
.Build();
await scheduler.ScheduleJob(trigger);
return RedirectToAction("ExecutingJobs");
}
因為Quartz.Net本身目的是排程功能,而我們希望的Task只是單純的執行一次,因此Trigger使用Simple並且設定WithRepeatCount為0,那麼Trigger執行後即會被Quartz.Net刪除。
最後提供一個UI用來顯示目前執行中的狀態
public async Task<IActionResult> ExecutingJobs([FromServices] ISchedulerFactory schedulerFactory, CancellationToken cancellationToken)
{
var scheduler = await schedulerFactory.GetScheduler();
var executingJobs = await scheduler.GetCurrentlyExecutingJobs(cancellationToken);
var data = executingJobs.Select(x => new JobExecutionContextModel(x));
return View(data);
}
這樣就簡單快速的完成了一個可以在WEB長時程執行的機制並且能夠有UI反饋
當然Quartz.Net能做的不止於此,譬如說執行中的Task允許取消機制,實現如Windows排程功能,並且可以讓使用透過UI來操作,集群機制等等。